
#include "config.h"
#include <algorithm>

#include <QtDebug>

namespace qyasa
{

struct IsFoundIn
{
    const std::vector<int32_t>& source;
    IsFoundIn(const std::vector<int32_t>& source) :
        source(source)
    {
    }

    bool operator()(const int32_t& value)
    {
        return source.end() != std::find(source.begin(), source.end(), value);
    }
};

template<typename T>
struct IfIdEqual
{
    const int32_t id;
    IfIdEqual(int32_t id) :
        id(id)
    {
    }
    bool operator()(const T& item)
    {
        return id == item.id;
    }
};

bool isNotReferencedItem(const yasa::configuration::ISTItem& item, const yasa::configuration::ISTCategory& category)
{
    // find item in current category, if not found look into sibling categories

    const std::vector<int32_t>::const_iterator iter = std::find(category.items.begin(), category.items.end(), item.id);
    bool isReferenced = category.items.end() != iter;
    if (!isReferenced)
    {
        // not found, need to go deeper
        for (const yasa::configuration::ISTCategory& cat : category.categories)
        {
            isReferenced = !isNotReferencedItem(item, cat);
            if (isReferenced)
            {
                break;
            }
        }
    }
    return !isReferenced;
}

void config::cleanupItems()
{
    // function will remove item if it is not referenced by any of categories
   items.erase(std::remove_if(items.begin(), items.end(), std::bind(isNotReferencedItem, std::placeholders::_1, theRoot)), items.end());
}

void config::cleanupCategory(yasa::configuration::ISTCategory& category)
{
    // prepare vector of item ids
    std::vector<int32_t> itemid;
	std::for_each(items.begin(), items.end(),
			[&](yasa::configuration::ISTItem& item) {itemid.push_back(item.id);});

    // find differences between category item references and items
    std::sort(category.items.begin(), category.items.end());
    std::sort(itemid.begin(), itemid.end());
    std::vector<int32_t> notFoundReferences;
    std::set_difference(category.items.begin(), category.items.end(), itemid.begin(), itemid.end(), std::back_inserter(notFoundReferences));

    // remove difference from category
    category.items.erase(std::remove_if(category.items.begin(), category.items.end(), IsFoundIn(notFoundReferences)), category.items.end());

    // go deeper
    std::for_each(category.categories.begin(), category.categories.end(), std::bind(&config::cleanupCategory, this, std::placeholders::_1));
}

void config::cleanupCategories()
{
    // function will remove references to non existent items in categories
    cleanupCategory(theRoot);
}

bool config::deleteItem(const int32_t& itemId)
{
    // find item by id
    std::vector<yasa::configuration::ISTItem>::iterator iter = std::find_if(items.begin(), items.end(),
            IfIdEqual<yasa::configuration::ISTItem> (itemId));
    if (items.end() == iter)
    {
        qWarning() << "cannot find item with id" << itemId;
        return false;
    }

    // remove item from list
    items.erase(iter);

    // remove reference to deleted item from categories
    cleanupCategories();
    return true;
}

yasa::configuration::ISTCategory* config::findCategory(const int32_t& categoryId)
{
    yasa::configuration::ISTCategory* category = nullptr;
    findCategoryInternal(theRoot, categoryId, &category);
    return category;
}

void config::findCategoryInternal(yasa::configuration::ISTCategory& node, const int32_t& categoryId, yasa::configuration::ISTCategory** category)
{
    // if this is a current node
    if (categoryId == node.id)
    {
        *category = &node;
    }
    else
    {
        // go deeper
        std::for_each(node.categories.begin(), node.categories.end(), std::bind(&config::findCategoryInternal, this, std::placeholders::_1, categoryId, category));
    }
}

void config::fromXml(const std::string& xmlData)
{
    yasa::configuration::Configuration::fromXml(xmlData, nullptr);
}

void config::copyCategory(yasa::configuration::ISTCategory& node)
{
    if (copyFileCallback)
    {
        copyFileCallback(node.imagePath);
        copyFileCallback(node.soundPath);
    }

    std::for_each(node.categories.begin(), node.categories.end(), std::bind(&config::copyCategory, this, std::placeholders::_1));
}

void config::copyItem(yasa::configuration::ISTItem& item)
{
    if (copyFileCallback)
    {
        copyFileCallback(item.imagePath);
        copyFileCallback(item.soundPath);
    }
}

std::string config::toXml()
{
    cleanupCategories();
    cleanupItems();

    // copy referenced files to config folder for categories
    copyCategory(theRoot);
    // and for items
    std::for_each(items.begin(), items.end(), std::bind(&config::copyItem, this, std::placeholders::_1));

    return yasa::configuration::Configuration::toXml(nullptr);
}

void config::addCategory(const yasa::configuration::ISTCategory& newCategory, const int32_t& parentCategoryId)
{
    yasa::configuration::ISTCategory* category = findCategory(parentCategoryId);
    if (category)
    {
        category->categories.push_back(newCategory);
        qDebug() << "category" << newCategory.name.c_str() << "added to" << category->name.c_str() << " parent category";
    }
    else
    {
        qCritical() << "failed to find category with id" << parentCategoryId;
        theRoot.categories.push_back(newCategory);
        qDebug() << "category" << newCategory.name.c_str() << "added to" << theRoot.name.c_str() << " parent category";
    }
}

void config::deleteCategoryInternal(yasa::configuration::ISTCategory& parent, const int32_t& categoryId)
{
    std::vector<yasa::configuration::ISTCategory>::iterator iter = std::find_if(parent.categories.begin(), parent.categories.end(),
            IfIdEqual<yasa::configuration::ISTCategory> (categoryId));

    if (parent.categories.cend() == iter)
    {
        std::for_each(parent.categories.begin(), parent.categories.end(),
                std::bind(&config::deleteCategoryInternal, this, std::placeholders::_1, categoryId));
    }
    else
    {
        // found it, delete it
        QString categoryName = iter->name.c_str();
        parent.categories.erase(iter);
        qDebug() << "category" << categoryName << "removed";
        cleanupItems();
    }
}

void config::deleteCategory(const int32_t& categoryId)
{
    deleteCategoryInternal(theRoot, categoryId);
}

void config::addItem(const yasa::configuration::ISTItem& item, const int32_t& categoryId)
{
    items.push_back(item);

    // put reference to this item into category
    yasa::configuration::ISTCategory* category = findCategory(categoryId);
    if (category)
    {
        category->items.push_back(item.id);
        qDebug() << "item" << item.name.c_str() << "added to category" << category->name.c_str();
    }
    else
    {
        qWarning() << "failed to find category with id " << categoryId;
        theRoot.items.push_back(item.id);
        qDebug() << "item" << item.name.c_str() << "added to root category" << theRoot.name.c_str();
    }
}

bool config::pasteItem(const int32_t& itemId, const int32_t& toCategoryId, const int32_t& fromCategoryId)
{
    if (!findItem(itemId))
    {
        qWarning() << "no such item" << itemId;
        return false;
    }

    yasa::configuration::ISTCategory* category = findCategory(toCategoryId);
    if (!category)
    {
        qCritical() << "no such category" << toCategoryId;
        return false;
    }

    // if category already contains that item, ignore paste
    const std::vector<int32_t>::const_iterator iter = std::find(category->items.begin(), category->items.end(), itemId);
    if (category->items.end() != iter)
    {
        qDebug() << "category already contains reference to this item";
        return false;
    }

    // the magic of paste
    category->items.push_back(itemId);

    qDebug() << "pasted item" << itemId << "into category" << category->name.c_str();

    // cut, if applicable
    yasa::configuration::ISTCategory* fromCategory = findCategory(fromCategoryId);
    if (fromCategory)
    {
        const std::vector<int32_t>::iterator iter = std::find(fromCategory->items.begin(), fromCategory->items.end(), itemId);
        if (fromCategory->items.end() == iter)
        {
            qWarning() << "item" << itemId << "not found in category to cut from";
        }
        else
        {
            fromCategory->items.erase(iter);
            qWarning() << "item" << itemId << "removed from category" << fromCategory->name.c_str();
        }
    }

    return true;
}

yasa::configuration::ISTItem* config::findItem(const int32_t& id)
{
    // find item by id
    std::vector<yasa::configuration::ISTItem>::iterator iter = std::find_if(items.begin(), items.end(),
            IfIdEqual<yasa::configuration::ISTItem> (id));

    return (items.end() == iter) ? nullptr : &*iter;
}

config::config(CopyFileCallback copyFileCallback) : copyFileCallback(copyFileCallback)
{

}

config::~config()
{
}

}
